/*
    REMIND.C

Written by DavidW
Started on 6/12/91
Resource file version 1/11/92
*/

#include <p_std.h>          /* basic Psion header file */
#include <p_object.h>       /* defines object oriented functions */
#include <p_gen.h>          /* defines E_GEN_ error values, etc */
#include <p_file.h>         /* defines E_FILE_ error values, etc */
#include <p_date.h>         /* defines date and time functions */
#include <rscfile.xg>       /* defines C_RSCFILE and related constants */
#include <wlib.h>           /* defines all Window Server library functions */
#include <hwif.h>           /* defines all Hwif Library functions */
#include "remind.rh"        /* definitions common to remind.rss & remind.c */
#include "remind.rsg"       /* GENERATED resource file header */

GLREF_D VOID *DatCommandPtr;    /* where run from */

GLDEF_D TEXT ** _cmds;          /* for menu commands */
GLDEF_D H_MENU_DATA * _mdata;   /* for menu titles */

LOCAL_C VOID *rcb;          /* resource file object handle */
LOCAL_D UINT MainWid;       /* ID of main window */

typedef struct
    {
    ULONG sdate;    /* date when reminder due */
    UBYTE ref;      /* TRUE iff this is the reminder queued to ALM: */
    UBYTE tlen;     /* length of text message */
    TEXT *pb;       /* the text message itself (another alloc cell) */
    } ENTRY;        /* represents one reminder */

LOCAL_D ENTRY table[500];       /* handle up to 500 reminders */
LOCAL_D UWORD nent=0;           /* number of entries */
LOCAL_D INT cent=0;             /* entry at cursor */
LOCAL_D INT tent=0;             /* entry at top of screen */

LOCAL_D ENTRY clipent;          /* entry in clipboard */

LOCAL_D UWORD show_statwin=FALSE;       /* is Status Window showing */
LOCAL_D UWORD ScreenWidth;              /* width of remaining screen */

LOCAL_D UWORD lastbflags=W_BORD_CORNER_4;   /* which kind of Border flags */

LOCAL_D VOID *ttH;              /* handle of Time Text object */

LOCAL_D VOID *aH;               /* handle of channel to ALM: alarm device */
LOCAL_D WORD almstat;           /* status word for ALM: services */
LOCAL_D WORD almactive;         /* active word for ALM: services */

LOCAL_D UWORD PrintRec;         /* which entry is being printed */
LOCAL_D UWORD PrintState;       /* which part of entry is being printed */
LOCAL_D TEXT PrintBuf[48];      /* holds text about to be printed */


LOCAL_C VOID LoadStr(INT rid,TEXT *pb)
    {   /* load resource into given buffer (assumed long enough) */
    while (p_entersend4(rcb,O_RS_READ_BUF,rid,pb)<0)
        hRequestReplacePack(rcb,DatCommandPtr);
    }

LOCAL_C TEXT *str(INT rid)
    {   /* load specified resource into scratch buffer and return address */
    LOCAL_D TEXT scratch[50];       /* static "scratch" buffer */

    LoadStr(rid,&scratch[0]);
    return(&scratch[0]);
    }

LOCAL_C INT cmp(INT r,VOID *pent)
    {   /* comparison function used in binary searching */
    return(table[r].sdate>(*(ULONG *)pent)? -1: 1);
    }

LOCAL_C INT LocateEntry(ULONG *psdate)
    {   /* find entry at (or nearest after) given date */
    INT dir;    /* direction to nearest hit (as given by p_bsrch) */
    INT ret;    /* nearest hit (as given by p_bsrch) */

    dir=p_bsrch(nent,cmp,&ret,psdate);
    if (dir>0)
        ret++;
    return(ret);
    }

LOCAL_C ENTRY *LocateRef(VOID)
    {   /* find the entry that was queued to ALM: */
    ENTRY *pent;

    pent=(&table[0]);
    while (!pent->ref)
        pent++;
    return(pent);
    }

LOCAL_C VOID RequeueAlarm(VOID)
    {   /* recalculate the next alarm due, and queue it (if any) */
    ULONG sdate;        /* current time and date */
    INT entno;          /* no of entry to queue */
    ENTRY *pent;        /* entry to queue */
    ULONG times[2];     /* needed for service to ALM: */
    TEXT buf[66];       /* needed for service to ALM: */

    if (almactive)
        {           /* cancel any alarm currently queued */
        p_iow2(aH,P_FCANCEL);
        p_waitstat(&almstat);
        almactive=FALSE;
        LocateRef()->ref=FALSE;
        }
    if (!nent)
        return;     /* no entries hence none to queue */
    sdate=p_date();
    entno=LocateEntry(&sdate);
    if (entno>=nent)
        return;     /* no entries with dates in the future */
    pent=(&table[entno]);
    *p_bcpy(&buf[0],pent->pb,pent->tlen)=0;     /* copy the text */
    pent->ref=TRUE;         /* mark this entry as the one queued */
    times[0]=pent->sdate;           /* fill in the time the alarm is due */
    times[1]=pent->sdate;           /* and the time to DISPLAY */
    p_ioc5(aH,P_FWRITE,&almstat,&times,&buf);
    almactive=TRUE;         /* set active word TRUE */
    }

LOCAL_C VOID UpdateTimeFormat(VOID)
    {               /* Update time-text object's record of time format */
    hTTSetFormat(ttH,H_TIME_FORMAT_NO_SECONDS|H_TIME_FORMAT_SUFFIX|
                            H_TIME_FORMAT_DAY_NAME|H_TIME_FORMAT_MONTH_NAME);
    }

LOCAL_C INT SdateToBuf(TEXT *pb,ULONG *psdate)
    {       /* convert time and date to text, as per system preferences */
    hTTSetTime(ttH,H_TIME_SET_SDATE,psdate);
    return(hTTSenseString(ttH,H_TIME_SENSE_BOTH,pb));
    }

LOCAL_C VOID SetGmode(INT gmode)
    {       /* set Gmode of current graphics context */
    G_GC gc;

    gc.gmode=gmode;
    gSetGC(0,G_GC_MASK_GMODE,&gc);
    }

LOCAL_C VOID SetBold(INT flag)
    {       /* prepare to print in bold (if flag TRUE) or non-bold */
    G_GC gc;

    gc.font=WS_FONT_BASE+flag;
    gSetGC(0,G_GC_MASK_FONT,&gc);
    }

LOCAL_C VOID GetCursorBox(INT i,P_RECT *pbox)
    {       /* where should the cursor be drawn */
    pbox->tl.x=4;
    pbox->br.x=10;
    pbox->tl.y=4+i*18;
    pbox->br.y=pbox->tl.y+9;
    }

LOCAL_C VOID DrawCursor(INT i)
    {       /* draw the cursor (character octal 34) */
    P_RECT box;

    GetCursorBox(i,&box);
    gPrintBoxText(&box,8,G_TEXT_ALIGN_LEFT,0,"\034",1);
    }

LOCAL_C VOID EraseCursor(INT i)
    {       /* remove the cursor (from the specified row) */
    P_RECT box;

    GetCursorBox(i,&box);
    gClrRect(&box,G_TRMODE_CLR);    /* clear all pixels in the rectangle */
    }

LOCAL_C VOID LineToScreen(INT y,INT xoff,TEXT *pb,INT tlen)
    {       /* print the given line of text on the screen */
    P_RECT box;

    box.tl.x=4;
    box.tl.y=4+y*9;
    box.br.x=ScreenWidth-4;
    box.br.y=box.tl.y+9;
    gPrintBoxText(&box,8,G_TEXT_ALIGN_LEFT,xoff,pb,tlen);
    }

LOCAL_C VOID ClearLine(INT y)
    {       /* clear all text from the given line */
    LineToScreen(y,0,NULL,0);
    }

LOCAL_C VOID DrawLine(INT i)
    {       /* draw the two lines of text for the specified entry */
    INT len;
    INT entno;
    ENTRY *pent;
    TEXT buf[48];

    entno=i+tent;       /* offset by no. of first on-screen entry */
    if (entno>=nent)
        {               /* no entry for this position - hence clear */
        ClearLine(i*2);
        ClearLine(i*2+1);
        }
    else
        {
        pent=(&table[entno]);
        SetBold(TRUE);      /* draw the date and time in bold */
        len=SdateToBuf(&buf[0],&pent->sdate);
        LineToScreen(i*2,10,&buf[0],len);
        SetBold(FALSE);     /* draw the text (and any cursor) in non-bold */
        if (entno==cent)
            DrawCursor(i);
        LineToScreen(i*2+1,22,pent->pb,pent->tlen);
        }
    }

LOCAL_C VOID Display(VOID)
    {       /* draw all four entry slots on the screen */
    INT i;

    if (!nent)
        {       /* no entries set - so display text to this effect */
        SetBold(TRUE);
        LineToScreen(0,10,str(STR_NO_REMINDERS_SET),16);
        ClearLine(1);       /* clear the line under the message */
        return;
        }
    for (i=0; i<4; i++)
        DrawLine(i);
    }

LOCAL_C INT ResizeWidth(UWORD width)
    {           /* try to change the width of the main display region */
    INT ret;
    W_WINDATA wd;

    wd.extent.tl.x=wd.extent.tl.y=0;
    wd.extent.width=width;
    wd.extent.height=80;
    wSetWindow(MainWid,W_WIN_EXTENT,&wd);
    ret=uErrorValue(wCheckPoint());     /* check whether successful */
    if (!ret)
        ScreenWidth=width;      /* only record new value if successful */
    return(ret);
    }

LOCAL_C INT ToggleStatusWindow(VOID)
    {       /* attempt to toggle the visibility of the status window */
    if (show_statwin)
        {       /* status window is currently showing */
        if (ResizeWidth(240))
            return(-1);
        wsDisable();        /* remove status window */
        }
    else
        {       /* status window not currently showing */
        if (ResizeWidth(189))
            return(-1);
        wsEnable();         /* display status window */
        }
    show_statwin^=TRUE;
    gBorder(lastbflags);        /* redraw the border */
    Display();                  /* redraw the main display */
    return(0);
    }

LOCAL_C VOID EnsureGoodTent(VOID)
    {                   /* find good value for tent - top entry on screen */
    if (nent<5)
        tent=0;         /* show ALL entries if less than five in total */
    else if (tent<cent-3)
        tent=cent-3;        /* cursor was off bottom of screen */
    else if (tent>cent)
        tent=cent;          /* cursor was off top of screen */
    }

LOCAL_C VOID AppendEntry(ENTRY *pent)
    {               /* add the given entry */
    INT tomove;

    if (!nent)
        cent=0;
    else
        {           /* find where to insert the entry */
        cent=LocateEntry(&pent->sdate);     /* uses binary search */
        tomove=nent-cent;           /* how many entries to "move along */
        p_bcpy(&table[cent+1],&table[cent],tomove*sizeof(ENTRY));
        }
    nent++;                     /* increment count of number of entries */
    table[cent]=(*pent);        /* copy the entry details into the table */
    EnsureGoodTent();           /* check whether need to scroll */
    Display();                  /* redraw the main display */
    RequeueAlarm();         /* ensure correct alarm is queued */
    }

LOCAL_C VOID DoDeleteEntry(VOID)
    {       /* delete the current entry */
    INT tomove;                 /* how many records to "move back" */

    p_free(table[cent].pb);     /* free alloc cell holding the text */
    nent--;                     /* decrement total count of entries */
    tomove=nent-cent;           /* now fill up the gap in the table */
    p_bcpy(&table[cent],&table[cent+1],tomove*sizeof(ENTRY));
    }

LOCAL_C VOID CleanEntry(ENTRY *pent)
    {               /* prepare a blank entry */
    p_bfil(pent,sizeof(ENTRY),0);
    pent->sdate=p_date()+3600;      /* seed to one hour in the future */
    }

LOCAL_C INT EditEntry(ENTRY *pent)
    {                   /* allow the user to edit an entry */
    P_DAYSEC ds;
    H_DI_DATE date;
    H_DI_TIME time;
    TEXT buf[66];
    H_DI_SEDIT sed;

    if (uOpenDialog(str(STR_REMINDER)))     /* title line of dialog */
        return(-1);
    p_bcpy(&buf[1],pent->pb,buf[0]=pent->tlen);
    sed.str=(&buf[0]);      /* prepare to edit the text of the reminder */
    sed.len=64;             /* maximum allowed length */
    sed.width=25;           /* width of "viewing" region */
    if (uAddDialogItem(H_DIALOG_SEDIT,str(STR_MESSAGE),&sed))
        return(-1);
    p_sttods(&pent->sdate,&ds);     /* crack into "time" and "date" */
    time.value=(&ds.sec);   /* prepare to edit the time of the reminder */
    time.low=0;
    time.high=P_NSECDAY-1;
    time.type=0;
    if (uAddDialogItem(H_DIALOG_TIME,str(STR_TIME),&time))
        return(-1);
    date.value=(&ds.day);   /* prepare to edit the date of the reminder */
    date.low=H_FIRST_SYS_DAY;
    date.high=H_LAST_SYS_DAY;
    if (uAddDialogItem(H_DIALOG_DATE,str(STR_DATE),&date))
        return(-1);
    if (uRunDialog()<=0)
        return(-1);         /* user cancelled (or insufficient memory) */
    p_dstost(&ds,&pent->sdate);     /* reconstitute system time */
    pent->pb=p_realloc(pent->pb,buf[0]);  /* check text alloc cell length */
    if (!pent->pb)
        {               /* no memory left to grow the text alloc cell */
        uErrorValue(E_GEN_NOMEMORY);
        return(-1);
        }
    pent->tlen=buf[0];
    p_bcpy(pent->pb,&buf[1],buf[0]);
    return(0);          /* signal successful edit */
    }

LOCAL_C VOID AddEntry(VOID)
    {                   /* the user wants to add an entry */
    ENTRY ent;

    CleanEntry(&ent);       /* prepare a "clean" initial starting point */
    if (EditEntry(&ent))    /* allow the user to edit it */
        return;
    AppendEntry(&ent);      /* insert the entry */
    }

LOCAL_C VOID InfoMsg(INT rid)
    {           /* present info message with text with given resource id */
    wInfoMsg(str(rid));
    }

LOCAL_C VOID UpdateEntry(VOID)
    {           /* the user wants to update the current entry */
    ENTRY *pent;
    ENTRY ent;

    if (!nent)
        {       /* no current entry to update */
        InfoMsg(STR_NO_ENTRY_UPDATE);
        return;
        }
    pent=(&table[cent]);        /* find the current entry */
    if (EditEntry(pent))        /* allow the user to edit it */
        return;
    ent=(*pent);
    pent->pb=0;
    DoDeleteEntry();        /* delete the existing entry... */
    AppendEntry(&ent);      /* ... and re-insert it with the new contents */
    }

LOCAL_C VOID DeleteEntry(VOID)
    {           /* the user wants to delete the current entry */
    TEXT ny[4];     /* for the characters 'n' then 'y' */
    TEXT yes[10];   /* for the string "yes" */
    TEXT no[10];    /* for the string "no" */

    if (!nent)
        {       /* no entry to delete */
        InfoMsg(STR_NO_ENTRY_DELETE);
        return;
        }
    if (uOpenDialog(str(STR_DELETE_REMINDER)))  /* title of dialog */
        return;
    LoadStr(STR_NY,&ny[0]);     /* find the text for the existing language */
    LoadStr(STR_NO,&no[0]);
    LoadStr(STR_YES,&yes[0]);
    if (uAddButtonList(&no[0],-ny[0],&yes[0],ny[1],NULL)) /* no-yes buttons */
        return;
    if (uRunDialog()!=ny[1])        /* check if user pressed 'y' button */
        return;
    DoDeleteEntry();            /* delete the entry */
    if (cent==nent && nent)     /* change the cursor position */
        cent=nent-1; 
    EnsureGoodTent();           /* check whether need to scroll display */
    Display();                  /* redraw main display area */
    RequeueAlarm();             /* recalculate which alarm to queue */
    }

LOCAL_C INT TakeCopy(ENTRY *targ,ENTRY *src)
    {               /* copy specified entry */
    targ->pb=p_alloc(src->tlen);    /* prepare an alloc cell for the text */
    if (!targ->pb)
        {
        uErrorValue(E_GEN_NOMEMORY);
        return(-1);
        }
    p_bcpy(targ->pb,src->pb,src->tlen);     /* copy the text */
    p_bcpy(targ,src,sizeof(ENTRY)-sizeof(TEXT *)); /* copy everything else */
    return(0);
    }

LOCAL_C VOID CopyEntry(VOID)
    {           /* the user wants to copy the current entry */
    ENTRY new;

    if (!nent)
        {       /* no current entry to copy */
        InfoMsg(STR_NO_ENTRY_COPY);
        return;
        }
    if (TakeCopy(&new,&table[cent]))
        return;     /* no memory to make copy */
    p_free(clipent.pb);     /* free any existing clipboard text */
    clipent=new;
    InfoMsg(STR_REMINDER_COPIED);       /* signal success to user */
    }

LOCAL_C VOID InsertEntry(VOID)
    {           /* the user wants to insert the clipboard contents */
    ENTRY new;

    if (!clipent.pb)
        {       /* the clipboard contents are empty */
        InfoMsg(STR_NO_ENTRY_INSERT);
        return;
        }
    if (TakeCopy(&new,&clipent))
        return;         /* no memory for new entry */
    AppendEntry(&new);      /* insert the entry */
    }

LOCAL_C VOID ExitIfConfirmed(VOID)
    {           /* the user wants to exit - request confirmation */
    H_DI_TEXT txt;
    TEXT cancel[10];        /* for the text "cancel" */
    TEXT exit[10];          /* for the text "exit" */

    if (nent)
        {           /* only need confirmation if there are entries */
        if (uOpenDialog(str(STR_WARNING)))  /* dialog title */
            return;
        txt.str=str(LSTR_DELETE_ALL);   /* lbc text from resource file */
        txt.type=H_DTEXT_ALIGN_CENTRE;  /* centre alignment */
        if (uAddDialogItem(H_DIALOG_TEXT,0,&txt))
            return;
        LoadStr(STR_CANCEL,&cancel[0]);     /* load correct language text */
        LoadStr(STR_EXIT,&exit[0]);
        if (uAddButtonList(&cancel[0],W_KEY_ESCAPE,
                                        &exit[0],W_KEY_RETURN,NULL))
            return;
        if (uRunDialog()!=W_KEY_RETURN)
            return;         /* the user failed to confirm */
        }
    p_exit(0);              /* exit cleanly */
    }

LOCAL_C INT PrintLine(H_PRINT *pr)
    {                   /* print next line of data */
    ENTRY *pent;

    pent=(&table[PrintRec]);        /* which entry to print */
    switch (PrintState++)
        {
    case 0:         /* need to print the date and time */
        pr->blen=SdateToBuf(&PrintBuf[0],&pent->sdate);
        pr->buf=(&PrintBuf[0]);
        pr->style|=H_PRINT_STY_BOLD;    /* print date and time in bold */
        break;
    case 1:         /* need to print the text */
        pr->buf=pent->pb;
        pr->blen=pent->tlen;
        break;
    case 2:         /* gap between entries */
        pr->blen=0;
        PrintState=0;
        PrintRec++;
        return(PrintRec!=nent);     /* return FALSE if no more to print */
        }
    pr->flags|=H_PRINT_KEEP;/* keep different parts of entry on same page */
    return(TRUE);           /* return TRUE since printing not yet finished */
    }

LOCAL_C VOID PrintAll(VOID)
    {               /* the user wants to print the reminders */
    if (!nent)
        {           /* no entries to print */
        InfoMsg(STR_NOTHING_PRINT);
        return;
        }
    PrintRec=0;     /* initialise print state variables */
    PrintState=0;
    hPrint(PrintLine);      /* specify call-back function */
    }

LOCAL_C VOID ManageCommand(INT index)
    {               /* switch on command index to appropriate function */
    switch (index)
        {
    case ITEMS_BEFORE_EDIT:
        InsertEntry();
        break;
    case ITEMS_BEFORE_EDIT+1:
        CopyEntry();
        break;
    case ITEMS_BEFORE_EDIT+2:
        UpdateEntry();
        break;
    case ITEMS_BEFORE_EDIT+3:
        AddEntry();
        break;
    case ITEMS_BEFORE_EDIT+4:
        DeleteEntry();
        break;
    case ITEMS_BEFORE_SPECIAL:
        PrintAll();
        break;
    case ITEMS_BEFORE_SPECIAL+1:
        hPrintSetupDialog();
        break;
    case ITEMS_BEFORE_SPECIAL+2:
        ExitIfConfirmed();
        break;
        }
    }

LOCAL_C VOID TryExecuteCommand(INT keycode)
    {      /* try to find command to execute matching accelerator passed */
    keycode=uLocateCommand(keycode);        /* look up accelerator table */
    if (keycode>=0)                 /* found - keycode converted to index */
        ManageCommand(keycode);
    }

LOCAL_C VOID DoScroll(INT dely)
    {                   /* scroll the display vertically by given amount */
    P_POINT offset;         /* offset to scroll */
    P_RECT src;             /* source region for scroll */

    offset.x=0;
    offset.y=dely*18;       /* each entry is two lines each 9 pixels high */
    src.tl.x=4;
    src.br.x=ScreenWidth-4;
    if (dely>0)
        {               /* scrolling down - so exclude bottom from source */
        src.tl.y=4;
        src.br.y=4+(4-dely)*18;
        }
    else
        {               /* scrolling up - so exclude top from source */
        src.tl.y=4-dely*18;
        src.br.y=4+4*18;
        }
    wScrollRect(MainWid,&src,&offset);
    }

LOCAL_C VOID MakeHighlight(INT newcent)
    {   /* change the cursor position as specified, with smooth scrolling */
    INT toscroll;       /* amount of scroll needed (if any) */

    if (!nent)
        return;         /* no entries - hence nothing to do */
    if (newcent==cent)
        return;         /* cursor already at specified position */
    EraseCursor(cent-tent);     /* remove existing cursor */
    toscroll=tent-newcent;
    if (toscroll>0)
        {               /* new cursor position off top of screen */
        tent-=toscroll;     /* adjust record of entry at top of screen */
        if (toscroll<4)
            DoScroll(toscroll);     /* only actually scroll if less than 4 */
        else
            toscroll=4; /* toscroll now holds number of lines to redraw */
        while (toscroll)
            DrawLine(--toscroll);       /* redraw lines not scrolled */
        }
    else
        {
        toscroll=-3-toscroll;
        if (toscroll>0)
            {               /* new cursor position off bottom of screen */
            tent+=toscroll; /* adjust record of entry at top of screen */
            if (toscroll<4)
                DoScroll(-toscroll);
            else
                toscroll=4;
            while (toscroll)
                DrawLine(4-toscroll--);     /* redraw lines not scrolled */
            }
        }
    cent=newcent;
    DrawCursor(cent-tent);      /* draw the cursor back in */
    }

LOCAL_C VOID ScrollPageBy(INT delta)
    {                   /* implementation of PgUp / PgDn */
    if (!nent)
        return;         /* no entries to display */
    if (delta<(-3))
        delta=(-3);     /* scroll at most three records */
    else if (delta>3)
        delta=3;
    tent+=delta;
    cent+=delta;
    Display();
    }

LOCAL_C VOID HandleAlarmEvent(VOID)
    {               /* an alarm has expired (or time has been put back) */
    ENTRY *pent;            /* the alarm that was queued */
    INT expired;            /* whether alarm really did expire */

    pent=LocateRef();
    expired=(pent->sdate<=p_date());    /* find if alarm time has elapsed */
    almactive=FALSE;
    pent->ref=FALSE;
    RequeueAlarm();         /* find which is next alarm to queue */
    if (expired)
        {
        uForceToFront();                    /* come into foreground */
        MakeHighlight(pent-(&table[0]));    /* highlight the elapsed alarm */
        UpdateEntry();                  /* allow user to edit the reminder */
        }
    }

LOCAL_C VOID CheckBorderArrows(VOID)
    { /* see if border needs redisplaying (on account of "scroll arrows") */
    INT bflags;         /* the flags that ought to be displayed */

    if (!nent)
        return;     /* no entries */
    if (tent)       /* check if more entries off screen top */
        bflags=W_BORD_CORNER_4|W_BORD_TOP_ON;
    else
        bflags=W_BORD_CORNER_4|W_BORD_TOP_OFF;
    if (tent+4<nent)    /* check if more entries off screen bottom */
        bflags|=W_BORD_BOT_ON;
    else
        bflags|=W_BORD_BOT_OFF;
    if (bflags!=lastbflags)     /* check if border needs to be redrawn */
        gBorder(lastbflags=bflags);
    }

LOCAL_C VOID MainLoop(VOID)
    {                   /* central event processing loop */
    INT ret;
    WMSG_KEY key;       /* to receive keypress */
    WORD keystat;       /* status word for keypress */
    WORD keyactive;     /* active word for keypress */

    keyactive=FALSE;        /* no keyboard read queued */
    FOREVER
        {
        CheckBorderArrows();    /* update border arrow display if changed */
        if (keyactive)
            wFlush();       /* flush Window Server function buffer */
        else
            {               /* queue a read for another keypress */
            uGetKeyA(&keystat,&key);
            keyactive=TRUE;
            }
        p_iowait();         /* wait for event */
        if (keystat==E_FILE_PENDING)
            {               /* alarm event must have occurred */
            HandleAlarmEvent();
            continue;
            }
        keyactive=FALSE;    /* keypress must have been received */
        if (key.keycode&W_EVENT_KEY)
            {
            if (key.keycode==CONS_EVENT_FOREGROUND)
                {           /* check whether system time format changed */
                UpdateTimeFormat();
                Display();
                }
            }
        else if (key.keycode&W_SPECIAL_KEY)     /* Psion key pressed */
            TryExecuteCommand(key.keycode&(~W_SPECIAL_KEY));
        else
            {
            switch (key.keycode)
                {
            case W_KEY_MENU:
                if (key.modifiers&W_CTRL_MODIFIER)
                    {           /* toggle status window visibility */
                    SetGmode(G_TRMODE_CLR);
                    gBorder(lastbflags&~(W_BORD_TOP_OFF|W_BORD_BOT_OFF));
                    SetGmode(G_TRMODE_SET);
                    ToggleStatusWindow();
                    }
                else
                    {           /* display menu bar */
                    ret=uPresentMenus();
                    if (ret>0)
                        TryExecuteCommand(ret);
                    }
                break;
            case W_KEY_DELETE_LEFT:
                DeleteEntry();  /* user wants to delete entry */
                break;
            case W_KEY_RETURN:
                if (!nent || key.modifiers&W_SHIFT_MODIFIER) 
                    AddEntry();     /* user wants to add entry */
                else
                    UpdateEntry();  /* user wants to update entry */
                break;
            case W_KEY_UP:
                if (cent)
                    MakeHighlight(cent-1);
                break;
            case W_KEY_DOWN:
                if (cent<nent-1)
                    MakeHighlight(cent+1);
                break;
            case W_KEY_PAGE_UP:
                if (key.modifiers&W_CTRL_MODIFIER)
                    MakeHighlight(0);
                else if (!tent)
                    MakeHighlight(0);
                else
                    ScrollPageBy(-tent);
                break;
            case W_KEY_PAGE_DOWN:
                if (key.modifiers&W_CTRL_MODIFIER)
                    MakeHighlight(nent-1);
                else if (tent+4>=nent)
                    MakeHighlight(nent-1);
                else
                    ScrollPageBy(nent-tent-4);
                break;
                }
            }
        }
    }

LOCAL_C VOID CreateGC(VOID)
    {     /* attempt to create default graphics context on main window */
    INT hgc;        /* graphics context handle (if successful) */

    hgc=gCreateGC0(MainWid);
    if (hgc<0)
        p_exit(hgc);
    }

LOCAL_C VOID *InitRcb(VOID)
    {               /* create and initialise resource file object */
    INT ret;
    VOID *rcb;      /* resource file object handle */

    rcb=p_new(1,C_RSCFILE);
    ret=p_entersend3(rcb,O_RS_INIT,DatCommandPtr);  /* pass file name */
    if (ret<0)          /* no resource file found */
        p_exit(ret);    /* fatal (programmer) error */
    return(rcb);
    }

LOCAL_C H_MENU_DATA *LoadMenuTitles(VOID *rcb,INT rid)
    {       /* load menu titles from specified resource file */
    H_MENU_DATA *pmdata,*pi;    /* for created alloc cell */
    TEXT *titles;               /* for loaded alloc cell */
    WORD count;                 /* how many menus */
    INT ret;

    ret=p_entersend4(rcb,O_RS_READ,rid,&titles);
    if (ret<0)
        return(NULL);           /* couldn't access resource file */
    count=(*(WORD *)titles);    /* first WORD gives count of menus */
    pmdata=p_alloc((count+1)*sizeof(H_MENU_DATA));
    if (!pmdata)
        {                   /* no memory to alloc H_MENU_DATA array */
        p_free(titles);     /* clean up partially constructed data */
        return(NULL);
        }
    pi=pmdata;          /* prepare to walk through H_MENU_DATA array */
    titles+=2;          /* skip over leading WORD count */
    while (count--)         /* for each menu */
        {
        pi->title=titles;           /* store pointer to title text */
        titles+=p_slen(titles)+1;   /* skip the text and terminating zero */
        pi->nlines=(*titles++);     /* store the number of lines */
        pi++;           /* increment to next H_MENU_DATA element */
        }
    pi->title=0;        /* place NULL at end of array */
    return(pmdata);     /* return pointer to created struct */
    }

LOCAL_C TEXT **LoadMenuCommands(VOID *rcb,INT rid)
    {       /* load menu commands from specified resource file */
    TEXT **pcmds,**pi;          /* for created alloc cell */
    TEXT *cmds;                 /* for loaded alloc cell */
    WORD count;                 /* how many commands */
    INT ret;

    ret=p_entersend4(rcb,O_RS_READ,rid,&cmds);
    if (ret<0)
        return(NULL);           /* couldn't access resource file */
    count=(*(WORD *)cmds);      /* first WORD gives count of commands */
    pcmds=p_alloc((count+1)*sizeof(TEXT *));
    if (!pcmds)
        {                   /* no memory to allow TEXT * array */
        p_free(cmds);       /* clean up partially constructed data */
        return(NULL);
        }
    pi=pcmds;           /* prepare to walk through TEXT * array */
    cmds+=2;            /* skip over leading WORD count */
    while (count--)         /* for each menu command */
        {
        *pi++=cmds;             /* store pointer to command details */
        cmds+=p_slen(cmds)+1;   /* skip the text and terminating zero */
        }
    *pi=0;              /* place NULL at end of array */
    return(pcmds);      /* return pointer to created struct */
    }

LOCAL_C VOID LoadMenus(VOID)
    {               /* load all menu data */
    rcb=InitRcb();      /* create and initialise resource file object */
    _cmds=LoadMenuCommands(rcb,REMIND_CMDS);    /* load commands */
    _mdata=LoadMenuTitles(rcb,REMIND_MENUS);    /* load menu titles */
    if (!_cmds || !_mdata)          /* test if either load failed */
        p_exit(E_GEN_START);        /* report fatal initialisation error */
    }

LOCAL_C VOID SpecificInit(VOID)
    {                   /* application initialisation specific to Remind */
    uEscape(FALSE);     /* disable Psion-Esc */
    LoadMenus();        /* load all menu data */
    MainWid=uFindMainWid();     /* find id of main display window */
    CreateGC();                 /* create default graphics context */
    p_open(&aH,"ALM:",-1);      /* open channel to ALM: device */
    ttH=hTTOpen();              /* open channel to time-text object */
    hTTSetAbbreviations(ttH,3,3);   /* set abbreviations to use */
    UpdateTimeFormat();             /* check current system time format */
    hCrackCommandLine();            /* crack incoming command line */
    if (ToggleStatusWindow())       /* attempt to display status window */
        p_exit(0);
    }

GLDEF_C VOID main(VOID)
    {                   /* the main() for all Hwif programs */
    uCommonInit();      /* initialisation common to all Hwif programs */
    SpecificInit();     /* initialisation specific to Remind */
    MainLoop();         /* the central event processing loop */
    }
